package com.meta.platform.login.plus.vcode.service.impl;

import java.util.*;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import com.meta.act.app.service.ISysConfigService;
import com.meta.act.app.service.ISysUserService;
import com.meta.act.app.service.SysLoginService;
import com.meta.act.app.service.impl.SysUserServiceImpl;
import com.meta.framework.act.entity.SysUser;
import com.meta.framework.common.utils.SecurityUtils;
import com.meta.framework.manager.factory.AsyncFactory;
import com.meta.framework.util.UserUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSONObject;

import com.meta.framework.act.model.LoginUser;
import com.meta.framework.common.constant.Constants;
import com.meta.framework.common.exception.BaseException;
import com.meta.framework.common.exception.CustomException;
import com.meta.framework.common.utils.DateUtils;
import com.meta.framework.common.utils.MessageUtils;
import com.meta.framework.define.DelFlagEnum;
import com.meta.framework.manager.AsyncManager;
import com.meta.framework.service.TokenService;
import com.meta.platform.login.plus.common.enums.VCodeChannel;
import com.meta.platform.login.plus.common.vo.LoginSuccessVo;
import com.meta.platform.login.plus.config.LoginPlusProperties;
import com.meta.platform.login.plus.security.LoginSimpleAuthenticationToken;
import com.meta.platform.login.plus.utils.VCodeUtils;
import com.meta.platform.login.plus.vcode.domain.VCodeSendRecord;
import com.meta.platform.login.plus.vcode.mapper.VCodeSendRecordMapper;
import com.meta.platform.login.plus.vcode.rest.req.GetVCodeCmd;
import com.meta.platform.login.plus.vcode.rest.req.VCodeLoginCmd;
import com.meta.platform.login.plus.vcode.service.VCodeService;
import com.meta.platform.sms.request.SendSmsParam;
import com.meta.platform.sms.service.InnerSendSmsService;

import lombok.extern.slf4j.Slf4j;

/**
 * 验证码业务处理
 *
 * @author pengjianfeng
 * @version 1.0
 * @date 2021/1/11 3:15 下午
 */
@Slf4j
@Service
public class VCodeServiceImpl implements VCodeService {
    
    @Resource
    private LoginPlusProperties loginPlusProperties;
    
    @Resource
    private VCodeSendRecordMapper vcodeSendRecordMapper;
    
    @Resource
    private InnerSendSmsService innerSendSmsService;
    
    @Resource
    private TokenService tokenService;
    
    @Resource
    private AuthenticationManager authenticationManager;

    @Resource
    private ISysUserService iSysUserService;

    @Resource
    private ISysConfigService iSysConfigService;

    @Resource
    private SysLoginService sysLoginService;

    /**
     * 获取验证码
     *
     * @param getVcodeCmd 获取验证码参数
     * @return
     */
    @Override
    public VCodeSendRecord getVCode(GetVCodeCmd getVcodeCmd) {
        
        if (null == getVcodeCmd) {
            return null;
        }

        //sysLoginService.checkCaptcha(getVcodeCmd.getPhoneNumber(), getVcodeCmd.getCode(), getVcodeCmd.getUuid());
        
        // 接收人
        String receiver = StringUtils.isNotBlank(getVcodeCmd.getMail()) ? getVcodeCmd.getMail() : getVcodeCmd.getPhoneNumber();
        
        // 随机生成6位验证码
        String code = VCodeUtils.getVCode(loginPlusProperties.getVCode().getLength());
        log.info("发送验证码，receiver={}, 验证码={}", receiver, code);
        
        // 验证码渠道
        VCodeChannel channel = StringUtils.isNotBlank(getVcodeCmd.getMail()) ? VCodeChannel.MAIL : VCodeChannel.SMS;
        
        if (loginPlusProperties.getVCode().isActive()) {
            if (VCodeChannel.SMS.equals(channel)) {
                SendSmsParam sendSmsParam = new SendSmsParam();
                sendSmsParam.setMobiles(Collections.singletonList(getVcodeCmd.getPhoneNumber()));
                sendSmsParam.setSmsKey(loginPlusProperties.getVCode().getSmsKey());
                Map<String, Object> hashMap = new HashMap<>();
                hashMap.put(loginPlusProperties.getVCode().getCodePlaceholderName(), code);
                hashMap.put(loginPlusProperties.getVCode().getExpiredPlaceholderName(), loginPlusProperties.getVCode().getExpiredTime());
                sendSmsParam.setSmsParams(JSONObject.toJSONString(hashMap));
                
                // 发送验证码
                this.innerSendSmsService.sendSms(sendSmsParam);
            } else {
                throw new RuntimeException("暂不支持邮件渠道发送验证码");
            }
        }
        
        VCodeSendRecord vcodeSendRecord = new VCodeSendRecord();
        vcodeSendRecord.setChannel(channel.getCode())
            .setReceiver(receiver)
            .setVCode(String.valueOf(code))
            .setExpiredTime(DateUtils.addMinutes(DateUtils.getNowDate(), loginPlusProperties.getVCode().getExpiredTime()));
        
        // 保存验证码记录
        this.vcodeSendRecordMapper.insertSelective(vcodeSendRecord);
        return loginPlusProperties.getVCode().isActive() ? null : vcodeSendRecord;
    }
    
    /**
     * 验证验证码
     *
     * @param receiver 手机号码 or 邮箱
     * @param vCode 验证码
     * @return
     */
    @Override
    public boolean verifyVCode(String receiver, String vCode) {
        
        if (StringUtils.isBlank(receiver)) {
            log.error("VCodeServiceImpl.verifyVCode error, param receiver is empty");
            return false;
        }
        
        VCodeSendRecord vcodeSendRecord = this.vcodeSendRecordMapper.selectOne(receiver);
        if (null == vcodeSendRecord) {
            log.error("VcodeServiceImpl.verifyVcode error, no send vcode, receiver={}", receiver);
            return false;
        }
        
        if (vcodeSendRecord.getStatus() == DelFlagEnum.I.getCode()) {
            log.info("验证码已验证，请重新获取验证码，receiver={}, vcode={}", receiver, vcodeSendRecord.getVCode());
            throw new BaseException("验证码已验证，请重新获取验证码");
        }
        
        if (DateUtils.getNowDate().compareTo(vcodeSendRecord.getExpiredTime()) > 0) {
            log.info("验证码已过期，receiver={}, vcode={}", receiver, vcodeSendRecord.getVCode());
            throw new BaseException("验证码过期");
        }
        
        if (!vcodeSendRecord.getVCode().equals(vCode)) {
            log.info("验证码错误，receiver={}, reqCode={}, vcode={}", receiver, vCode, vcodeSendRecord.getVCode());
            throw new BaseException("验证码错误");
        }
        
        VCodeSendRecord record = new VCodeSendRecord();
        record.setId(vcodeSendRecord.getId());
        record.setStatus(DelFlagEnum.I.getCode());
        return this.vcodeSendRecordMapper.updateByIdSelective(record) > 0;
    }
    
    /**
     * 验证码登录
     *
     * @param loginCmd 登录参数
     * @return
     */
    @Override
    public LoginSuccessVo login(VCodeLoginCmd loginCmd) {

        String phoneNumber = loginCmd.getUserName();
        boolean verified = this.verifyVCode(phoneNumber, loginCmd.getVcode());
        if (!verified) {
            throw new CustomException("验证码错误");
        }

        SysUser sysUser = this.iSysUserService.selectUserByUserName(phoneNumber);
        if(Objects.isNull(sysUser)){
            String password = this.iSysConfigService.selectConfigByKey("sys.user.initPassword");
            SysUser user = new SysUser();
            user.setNickName(UserUtil.defaultUserName(phoneNumber));
            user.setUserName(phoneNumber);
            user.setPhonenumber(phoneNumber);
            user.setPassword(SecurityUtils.encryptPassword(password));
            user.setTenantId(1L);

            this.iSysUserService.insertUser(user);
            sysUser = user;
        }
        
        // 用户验证
        Authentication authentication = null;
        try {
            authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(phoneNumber, "CUSTOM_LOGIN_SMS"));
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        catch (Exception e) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(phoneNumber, Constants.LOGIN_FAIL, e.getMessage()));
            throw new CustomException(e.getMessage());
        }
        AsyncManager.me()
            .execute(
                AsyncFactory.recordLogininfor(phoneNumber, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser)authentication.getPrincipal();
        
        return new LoginSuccessVo().setToken(tokenService.createToken(loginUser)).setUser(loginUser);
    }
}
